<template>
  <div class="cloud_ai_3d">
    <div class="model-box">
      <div class="model_btn" :span="4" style="z-index: 99">
        <el-card class="model_switch_box">
          <h3>{{ $t('caseorder.yulan.yulan129') }}</h3>
          <div class="model_switch_item" v-for="(item, index) in fileListArr" :key="index">
            <el-tooltip :content="item.typeName" placement="top">
              <span style="max-width: 280px">{{ item.typeName }}</span>
            </el-tooltip>
            <el-switch v-model="item.show" active-color="#003a64" @change="modelSwitch(item)" />
          </div>
        </el-card>
        <el-card class="model_switch_box" style="margin-top: 20px; width: 300px; height: 300px">
          <h3>雕刻工具</h3>
          <div style="display: flex">
            <el-image
              v-for="(item, index) in btn_imgs"
              :key="item.img"
              :src="currentIndex == index && openCarve ? item.img_active : item.img"
              :class="{
                model_switch_img: true,
                active: currentIndex == index && openCarve,
              }"
              @click="modelClick(index)"
            />
          </div>
          <div class="brush-box">
            <label for="笔刷范围" class="brush_label">笔刷范围(px)</label>
            <el-input-number
              class="brush_num"
              v-model="brushNumber"
              controls-position="right"
              :step="0.1"
              :min="0.2"
              :max="4"
            />
            <el-progress :percentage="brushNumberPercentage" :show-text="false" />
          </div>

          <div class="brush-box">
            <label class="brush_label" for="笔刷强调">笔刷强调(mm)</label>
            <el-input-number
              class="brush_num"
              v-model="brushIntense"
              controls-position="right"
              :min="0.01"
              :max="0.2"
              label="笔刷强调"
              :step="0.01"
            />
            <el-progress :percentage="brushIntensePercentage" :show-text="false" />
          </div>
        </el-card>
      </div>
      <div ref="container" id="container" style="width: 100%; height: 100%"></div>
    </div>
  </div>
</template>

<script>
import * as THREE from 'three';

import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer.js';
import { ShaderPass } from 'three/examples/jsm/postprocessing/ShaderPass.js';

import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader';
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader';
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';

import { createLight, createCamera, createRender, createControls } from './cloudAIConfig.js';
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer.js';
import { cloneDeep } from 'lodash';
import { setToothNumber } from './util.js';
import { vertexShader, fragmentShader } from '@/config/3D';
import './ai.scss';

let scene = new THREE.Scene();
let raycaster = new THREE.Raycaster();

let openaxes = false;
const groups = [];
let initializedModule = null;

const depthTexture = new THREE.DepthTexture();
depthTexture.type = THREE.UnsignedShortType;
depthTexture.minFilter = THREE.NearestFilter;
depthTexture.magFilter = THREE.NearestFilter;
const cssRenderer = new CSS2DRenderer();
const getUniformsPartParams = (texture = null) => {
  //有纹理时一套亮度，顶点颜色和无纹理一套亮度
  return {
    lightColor: { value: new THREE.Color(0xffffff) }, // 光源颜色（白色）
    ambientStrength: { value: texture ? 1.0 : 0.7 },
    diffcoef: { value: texture ? 0.0 : 0.4 }, // 调大可让漫反射更明显（接近真实牙齿）
    specularStrength: { value: texture ? 0.6 : 0.1 }, // 镜面反射强度（牙齿高光不宜过强）
  };
};
export default {
  data() {
    return {
      uuid: null,
      toothNo: 15,
      aiCrownInfo: null,
      cloudAICon: '3D',
      iframeSrc: '',
      upperData: null,
      lowerData: null,
      brushNumber: 0.2,
      brushIntense: 0.01,
      fileListArr: [
        {
          typeName: '上颌',
          show: true,
        },
        {
          typeName: '下颌',
          show: true,
        },
        {
          typeName: '修复体',
          show: true,
        },
      ],
      animationFrameId: null,
      renderer: null,
      camera: null,
      controls: null,
      currentIndex: -1,
      openCarve: false,
      btn_imgs: [
        {
          img: require('@/assets/img/ai/increase.png'),
          img_active: require('@/assets/img/ai/increase_active.png'),
        },
        {
          img: require('@/assets/img/ai/reduce.png'),
          img_active: require('@/assets/img/ai/reduce_active.png'),
        },
        {
          img: require('@/assets/img/ai/smooth.png'),
          img_active: require('@/assets/img/ai/smooth_active.png'),
        },
      ],
      shadowVertices: [],
      shadowIndices: [],
      shadowNormals: [],
      originIndices: [],
      mouseMoveThrottle: null,
      lastMousePosition: { x: 0,
y: 0 },
      toothPosition: new THREE.Vector3(0, 0, 0),
    };
  },
  created() {
    (async () => {
      const moduleUrl = '/CrownDesign.js';

      try {
        // 加载 JS 模块
        const wasmModule = await import(/* webpackIgnore: true */ moduleUrl);
        initializedModule = await wasmModule.default();
      } catch (err) {}
    })();
  },
  mounted() {
    this.init();

    cssRenderer.setSize(window.innerWidth, window.innerHeight);
    cssRenderer.domElement.style.position = 'absolute';
    cssRenderer.domElement.style.top = '0';
    cssRenderer.domElement.style.pointerEvents = 'none'; // 避免遮挡点击
    document.body.appendChild(cssRenderer.domElement);

    const domElement = this.renderer.domElement;
    domElement.addEventListener('mousemove', this.handleMouseMove, false);
    domElement.addEventListener('click', this.handleMouseClick, false);
  },
  computed: {
    brushNumberPercentage() {
      return (this.brushNumber / 200) * 100;
    },
    brushIntensePercentage() {
      return (this.brushIntense / 200) * 100;
    },
  },
  methods: {
    init() {
      const element = document.getElementById('container');
      createRender(this, element); // 创建渲染器

      const { ambientLight, point } = createLight();
      scene.add(ambientLight);
      scene.add(point);

      const { camera } = createCamera(element); // 创建相机

      this.camera = camera;

      const { controls, axesHelper } = createControls(camera, this.renderer, openaxes); // 创建控件对象
      this.controls = controls;

      openaxes && scene.add(axesHelper);

      const defaultCameraPos = new THREE.Vector3(199.05, -0.93, -0.56);
      camera.position.set(defaultCameraPos.x, defaultCameraPos.y, defaultCameraPos.z);
      camera.up.set(0, 0, 1); // 确保 Y 轴向上
      camera.lookAt(0, 0, 0);
      controls.update();
      this.render(); // 渲染
      this.getParentMessage();

      this.controls.addEventListener('change', () => {
        this.camera.updateProjectionMatrix(); // 强制更新投影矩阵
      });
      window.addEventListener('resize', () => {
        const width = element.clientWidth;
        const height = element.clientHeight;
        camera.aspect = width / height;
        camera.updateProjectionMatrix();
        this.renderer.setSize(width, height);
      });
    },
    getParentMessage() {
      window.addEventListener('message', (event) => {
        console.log(event);
        if (event.data.type === 'FROM_UPLOADER') {
          const { upperData, lowerData, toothNo, meshFile, toothPosition, uuid } = event.data.data;
          this.fileExtensionName = upperData.name;
          this.uuid = uuid;
          this.toothNo = toothNo;
          // this.toothPosition = toothPosition;
          // 使用 Promise.all 并行加载模型
          const promises = [];
          if (['.ply', '.obj', '.stl'].includes(this.fileExtensionName)) {
            promises.push(
              this.loadModel(upperData, '上颌'),
              this.loadModel(lowerData, '下颌'),
              this.loadCrown(meshFile, toothPosition),
            );
          } else if (this.fileExtensionName.endsWith('.blzmesh')) {
            promises.push(
              this.handleBlzmeshData(upperData, '上颌'),
              this.handleBlzmeshData(lowerData, '下颌'),
              // this.loadCrown(meshFile, toothPosition),
            );
          }
          Promise.all(promises)
            .then(() => {
              // 所有模型加载完成后执行

              this.$message.success('模型加载完成');
            })
            .catch((error) => {
              console.error('加载模型出错:', error);
              this.$message.error('加载模型失败: ' + error.message);
            });
        }
      });
    },
    loadCrown(meshFile, toothPosition) {
      const url = `/renren-admin/huchaochao/mesh/download?uuid=${this.uuid}&toothNo=${this.toothNo}&fileName=${meshFile}`;

      let loader = null;
      if (this.fileExtensionName.endsWith('.ply')) {
        loader = new PLYLoader();
      } else if (this.fileExtensionName.endsWith('.stl')) {
        loader = new STLLoader();
      } else if (this.fileExtensionName.endsWith('.obj')) {
        loader = new OBJLoader();
      }
      const toothPosArray = toothPosition?.split(' ');

      loader.load(
        url,
        (geometry) => {
          console.log(geometry);
          const material = new THREE.MeshStandardMaterial({});
          material.side = THREE.DoubleSide;
          this.mesh = new THREE.Mesh(geometry);

          this.mesh['name'] = '修复体';

          scene.add(this.mesh);
          groups.push(this.mesh);
          setToothNumber(
            scene,
            new THREE.Vector3(
              Number(toothPosArray ? toothPosArray[0] : 0),
              Number(toothPosArray ? toothPosArray[1] : 0),
              Number(toothPosArray ? toothPosArray[2] : 0),
            ),
            // new THREE.Vector3(5.00678, -24.1286, 0.296553),
            this.toothNo,
          );
        },
        (onerror) => {
          console.log('onerror', onerror);
        },
      );
    },
    loadModel(file, type) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();

        reader.onload = (event) => {
          try {
            let loader = null;
            if (this.fileExtensionName.endsWith('.ply')) {
              loader = new PLYLoader();
            } else if (this.fileExtensionName.endsWith('.stl')) {
              loader = new STLLoader();
            } else if (this.fileExtensionName.endsWith('.obj')) {
              loader = new OBJLoader();
            }

            const geometry = loader.parse(event.target.result); // 使用parse方法解析内容
            const material = new THREE.MeshStandardMaterial({});
            material.side = THREE.DoubleSide;
            this.mesh = new THREE.Mesh(geometry, material);
            scene.add(this.mesh);
            this.mesh.name = type;
            this.mesh.typeName = type;

            groups.push(this.mesh);
            this.switchTexture();
            resolve();
          } catch (error) {
            reject(error);
          }
        };
        reader.onerror = () => {
          reject(new Error('文件读取失败'));
        };
        reader.readAsArrayBuffer(file);
      });
    },
    async handleBlzmeshData(file, type) {
      const arrayBuffer = await this.readFileAsArrayBuffer(file);
      this.handleBlzmeshData2(arrayBuffer, type);
    },
    readFileAsArrayBuffer(file) {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onload = () => resolve(reader.result);
        reader.onerror = reject;
        reader.readAsArrayBuffer(file);
      });
    },
    async handleBlzmeshData2(arrayBuffer, typeName) {
      // 解析 blzmesh 文件数据
      // const arrayBuffer = await this.readFileAsArrayBuffer(file);
      const dataView = new DataView(arrayBuffer);
      debugger;
      let offset = 0;
      offset += 7;

      // 读取缓存数据大小和图像数据大小
      const bufferSize = Number(dataView.getBigInt64(offset, true));
      offset += 8;
      const imgBufferSize = Number(dataView.getBigInt64(offset, true));
      offset += 8;

      // 提取网格数据
      const meshBuffer = arrayBuffer.slice(offset, offset + bufferSize);
      offset += bufferSize;

      // 提取图像数据(如果存在)
      let textureData = null;
      if (imgBufferSize > 0) {
        const imgBuffer = arrayBuffer.slice(offset, offset + imgBufferSize);
        try {
          // 加载纹理
          const texture = await this.loadTexture(imgBuffer);

          textureData = texture;
        } catch (error) {
          console.error('纹理加载失败:', error);
        }
      }

      // 解码网格数据
      try {
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath('https://oss.blzdentalcloud.com/threejs/');

        // 使用 Promise 包装 Draco 解码过程
        let geometry = await new Promise((resolve, reject) => {
          dracoLoader.decodeDracoFile(
            meshBuffer,
            (geometry) => {
              console.log(geometry, 33333);
              if (!geometry.attributes.normal) {
                geometry.computeVertexNormals(); // 自动计算顶点法线
                console.log('法线已重新计算');
              }
              resolve(geometry);
            },
            null,
            null,
            (errorCode) => {
              reject(new Error(`Draco解码失败，错误码: ${errorCode}`));
            },
          );
        });

        const material = new THREE.MeshStandardMaterial({});
        const mesh = new THREE.Mesh(geometry, material);
        mesh['typeName'] = typeName;
        groups.push(mesh);
        scene.add(mesh);
        this.switchTexture();
      } catch (error) {
        console.error('网格解码失败:', error);
        return null;
      }
    },
    async loadTexture(imgBuffer) {
      const blob = new Blob([imgBuffer], { type: 'image/png' });
      const url = URL.createObjectURL(blob);

      return new Promise((resolve, reject) => {
        const texture = new THREE.TextureLoader().load(
          url,
          () => {
            resolve(texture);
          },
          undefined,
          (error) => {
            reject(error);
          },
        );
      });
    },
    switchTexture() {
      groups.forEach((child) => {
        let hasVertexColors =
          child.geometry &&
          child.geometry.attributes &&
          child.geometry.attributes.color &&
          child.geometry.attributes.color.count > 0;
        const defaultColorValue = new THREE.Vector3(231.0 / 255.0, 223.0 / 255.0, 147.0 / 255.0);
        if (child.isMesh) {
          const hasTexture = !!child.texture;
          console.log(hasTexture, hasVertexColors, 22222);
          child.material = new THREE.ShaderMaterial({
            uniforms: {
              ...getUniformsPartParams(hasTexture),
              uTexture: { value: child.texture },
              useTexture: { value: hasTexture }, // 确保 useTexture 正确设置
              useVertexColor: { value: hasVertexColors }, // 根据是否有顶点颜色设置
              useModeTypeValue: { value: this.fileExtensionName.endsWith('.blzmesh') },
              lightPosition: {
                value: this.camera.position,
              },
              renderType: {
                value: 2,
              },
              textureType: {
                value: 0,
              },
              defaultColor: {
                value: defaultColorValue,
              },
            },
            vertexShader: vertexShader,
            fragmentShader: fragmentShader,
            side: THREE.DoubleSide, // 设置材质为双面
          });
          child.material.needsUpdate = true; // 确保材质更新
        }
      });
    },
    modelSwitch(item) {
      groups.map((child) => {
        if (child.name === item.typeName) {
          child.visible = item.show;
        }
      });
    },

    modelClick(index) {
      if (index == this.currentIndex || this.currentIndex == -1) {
        this.openCarve = !this.openCarve;
      }

      this.currentIndex = index;
    },
    handleMouseMove(event) {
      // 只有在开启雕刻模式时才处理事件
      if (!this.openCarve) return;
      this.onMouseMoveCarve(event);
    },

    handleMouseClick(event) {
      // 只有在开启雕刻模式时才处理事件
      if (!this.openCarve) return;
      this.onMouseClickCarve(event);
    },
    createProjectionTarget() {
      if (this.composer) return; // 如果已经创建则直接返回
      const renderTarget = new THREE.WebGLRenderTarget(window.innerWidth, window.innerHeight, {
        depthTexture: depthTexture,
        depthBuffer: true,
        stencilBuffer: false,
      });
      this.composer = new EffectComposer(this.renderer, renderTarget);
      this.composer.setSize(window.innerWidth, window.innerHeight);
      // 自定义渲染通道（渲染场景到目标纹理）
      const renderPass = new ShaderPass({
        uniforms: {},
        vertexShader:
          'void main() { gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); }',
        fragmentShader: 'void main() { gl_FragColor = vec4(1.0); }',
      });
      renderPass.render = (renderer, writeBuffer, readBuffer) => {
        renderer.setRenderTarget(renderTarget);
        renderer.render(scene, this.camera);
        renderer.setRenderTarget(null);
      };
      this.composer.addPass(renderPass);
      // 交线检测通道
      console.log(33333, this.brushNumber);
      this.intersectionPass = new ShaderPass({
        uniforms: {
          tDiffuse: { value: renderTarget.texture }, // 关联颜色纹理
          tDepth: { value: depthTexture }, // 关联深度纹理
          sphereCenter: { value: new THREE.Vector3() },
          sphereRadius: { value: this.brushNumber },
          resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
          invProjectionViewMatrix: { value: new THREE.Matrix4() },
        },
        vertexShader: `
                        varying vec2 vUv;
                        void main() {
                            vUv = uv;
                            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
                        }
                    `,
        fragmentShader: `
                        varying vec2 vUv;
                        uniform sampler2D tDiffuse;
                        uniform sampler2D tDepth;
                        uniform vec3 sphereCenter;
                        uniform float sphereRadius;
                        uniform vec2 resolution;
                        uniform mat4 invProjectionViewMatrix;

                        // 从深度恢复世界坐标
                        vec3 getWorldPosition(float depth, vec2 uv) {
                            vec3 ndc = vec3(uv * 2.0 - 1.0, depth * 2.0 - 1.0);
                            vec4 worldPos = invProjectionViewMatrix * vec4(ndc, 1.0);
                            worldPos.xyz /= worldPos.w;
                            return worldPos.xyz;
                        }

                        void main() {
                            // 读取颜色和深度
                            vec4 originalColor = texture2D(tDiffuse, vUv);
                            float depth = texture2D(tDepth, vUv).r;
                            vec3 worldPos = getWorldPosition(depth, vUv);
							
                            // 检测球体与模型的交点
                            float dist = distance(worldPos, sphereCenter);
                            float isIntersection = step(sphereRadius, dist) * step(dist, sphereRadius + 0.2);
							
                            // 叠加红色交线
                            vec3 lineColor = vec3(1.0, 0.0, 0.0);
                            vec3 finalColor = mix(originalColor.rgb, lineColor, isIntersection);
                            gl_FragColor = vec4(finalColor, 1.0);
                        }
                    `,
      });
      this.composer.addPass(this.intersectionPass);
      this.isIntersectionPassEnabled = true; // 启用交线检测
    },

    onMouseMoveCarve(event) {
      if (this.mouseMoveDebounceTimer) {
        clearTimeout(this.mouseMoveDebounceTimer);
      }

      if (!this.openCarve) {
        return;
      }
      // 设置新的防抖定时器
      this.mouseMoveDebounceTimer = setTimeout(() => {
        const clientX = event.clientX;
        const clientY = event.clientY;
        const domElement = this.renderer.domElement;
        const rect = domElement.getBoundingClientRect();
        const mousePickPos = {
          x: ((clientX - rect.left) / rect.width) * 2 - 1,
          y: -((clientY - rect.top) / rect.height) * 2 + 1,
        };
        raycaster.setFromCamera(mousePickPos, this.camera);

        const intersects = raycaster.intersectObjects(groups, true);
        console.log('intersects', intersects);
        if (intersects.length > 0) {
          const center = intersects[0].point;
          if (this.intersectionPass) {
            this.intersectionPass.material.uniforms.sphereCenter.value.copy(center);
            this.intersectionPass.material.uniforms.sphereCenter.needsUpdate = true;
          }
          // 只在需要时创建投影目标
          if (!this.isIntersectionPassEnabled) {
            this.createProjectionTarget();
          }
        } else {
          this.intersectionPass.material.uniforms.sphereCenter.value.set(1000, 1000, 1000);
        }
      }, 17); // 约60fps的延迟，可以根据需要调整
    },

    onMouseClickCarve(event) {
      console.log('onMouseClickCarve');

      if (!this.openCarve) {
        return;
      }
      const clientX = event.clientX;
      const clientY = event.clientY;
      const domElement = this.renderer.domElement;
      const rect = domElement.getBoundingClientRect();
      const mousePickPos = {
        x: ((clientX - rect.left) / rect.width) * 2 - 1,
        y: -((clientY - rect.top) / rect.height) * 2 + 1,
      };
      raycaster.setFromCamera(mousePickPos, this.camera);
      const stime = new Date();
      const intersects = raycaster.intersectObjects(groups, true);
      console.log(cloneDeep(groups), 55555);
      if (intersects?.length > 0) {
        console.log('intersects', intersects);
        const {
          face,
          point,
          object: { typeName },
        } = intersects[0];
        const point2 = [point.x, point.y, point.z];
        const currentMesh = groups.find((item) => item.typeName === typeName);
        console.log('currentMesh', groups);
        const verticesPtr = currentMesh.geometry.attributes.position.array;
        const indexsPtr = currentMesh.geometry.index.array;
        const EngraveDetection = initializedModule.Engrave;

        const engrave = new EngraveDetection();
        const copyToWasm = (data) => {
          const numBytes = data.length * data.BYTES_PER_ELEMENT;
          const ptr = initializedModule._malloc(numBytes);
          const heap = new Uint8Array(initializedModule.HEAPU8.buffer, ptr, numBytes);
          heap.set(new Uint8Array(data.buffer));
          return ptr;
        };
        // console.log('verticesPtr', verticesPtr, 'facesPtr', facesPtr);
        const vPtr = copyToWasm(verticesPtr);
        const fPtr = copyToWasm(indexsPtr);
        console.log('vPtr', vPtr, 'fPtr', fPtr);
        engrave.setEngravedMesh(vPtr, verticesPtr.length, fPtr, indexsPtr.length);

        const computeRes = engrave.compute(
          point2,
          this.brushNumber,
          this.brushIntense,
          this.currentIndex,
        );
        console.log(point2, this.brushNumber, this.brushIntense, this.currentIndex);
        const { partialVertCoords, partialVertIndices, partialVertNormals } =
          engrave.getEngravedData();
        console.log('partialVertCoords', partialVertCoords);
        console.log('partialVertIndices', partialVertIndices);
        console.log('partialVertNormals', partialVertNormals);
        console.log(currentMesh);
        partialVertIndices.forEach((i, idx) => {
          currentMesh.geometry.attributes.position.array[3 * i] = partialVertCoords[3 * idx];
          currentMesh.geometry.attributes.position.array[3 * i + 1] =
            partialVertCoords[3 * idx + 1];
          currentMesh.geometry.attributes.position.array[3 * i + 2] =
            partialVertCoords[3 * idx + 2];

          currentMesh.geometry.attributes.normal.array[3 * i] = partialVertNormals[3 * idx];
          currentMesh.geometry.attributes.normal.array[3 * i + 1] = partialVertNormals[3 * idx + 1];
          currentMesh.geometry.attributes.normal.array[3 * i + 2] = partialVertNormals[3 * idx + 2];
        });

        currentMesh.geometry.attributes.position.needsUpdate = true;
        currentMesh.geometry.index.needsUpdate = true;
        currentMesh.geometry.attributes.normal.needsUpdate = true;

        initializedModule._free(vPtr);
        initializedModule._free(fPtr);
        engrave.delete();
        console.log(groups, 'groups after openCarve');
      }
    },
    render() {
      requestAnimationFrame(this.render);

      this.controls.update();
      if (this.toothPosition) {
        cssRenderer.render(scene, this.camera);
      }
      if (this.intersectionPass && this.intersectionPass.material.uniforms.sphereRadius) {
        if (this.intersectionPass.material.uniforms.sphereRadius.value !== this.brushNumber) {
          this.intersectionPass.material.uniforms.sphereRadius.value = this.brushNumber;
          this.intersectionPass.material.uniforms.sphereRadius.needsUpdate = true;
        }
      }

      if (this.intersectionPass) {
        this.intersectionPass.material.uniforms.invProjectionViewMatrix.value
          .multiplyMatrices(this.camera.projectionMatrix, this.camera.matrixWorldInverse)
          .invert();
        this.intersectionPass.material.uniforms.invProjectionViewMatrix.needsUpdate = true;
      }

      // 仅在启用交线检测且有变化时才使用composer渲染
      if (this.composer && this.isIntersectionPassEnabled) {
        this.composer.render();
      } else {
        this.renderer.render(scene, this.camera);
      }
    },
  },
  beforeDestroy() {
    if (this.controls) this.controls.dispose();
    if (this.renderer) this.renderer.dispose();
    if (this.renderer && this.renderer.domElement) {
      const domElement = this.renderer.domElement;
      domElement.removeEventListener('mousemove', this.handleMouseMove, false);
      domElement.removeEventListener('click', this.handleMouseClick, false);
    }
  },
};
</script>
